/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Eclipse Public License (EPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/eclipse-1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.mmtk.plan.scale.colors;

import org.mmtk.plan.scale.Scale;
import org.mmtk.plan.scale.ScaleTrace;
import org.mmtk.plan.scale.ScaleTraceLocal;
import org.mmtk.plan.scale.utils.ScaleLogger;
import org.mmtk.policy.Space;
import org.mmtk.utility.Log;
import org.mmtk.utility.options.Options;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.ObjectReference;
import org.vmmagic.unboxed.ObjectReferenceArray;

/**
 * This class implements the thread-local functionality for a transitive closure
 * over a mark-sweep space.
 */
@Uninterruptible
public class SColorsTraceLocal extends ScaleTraceLocal {
	/****************************************************************************
	 * Instance fields
	 */
	/**
	 * Constructor
	 */
	public SColorsTraceLocal(String name, ScaleTrace trace) {
		this(name, SColors.SCAN_COLOR, trace);
	}

	public SColorsTraceLocal(String name, int scan, ScaleTrace trace) {
		super(name, scan, trace);
	}

	private long tracedDeadObjects;
	public long getTracedDeadObjects() {
		return tracedDeadObjects;
	}
	/****************************************************************************
	 * Externally visible Object processing and tracing
	 */

	/**
	 * This method is the core method during the trace of the object graph. The
	 * role of this method is to:
	 * 
	 * 1. Ensure the traced object is not collected. 2. If this is the first
	 * visit to the object enqueue it to be scanned. 3. Return the forwarded
	 * reference to the object.
	 * 
	 * In this instance, we refer objects in the mark-sweep space to the msSpace
	 * for tracing, and defer to the superclass for all others.
	 * 
	 * @param object
	 *            The object to be traced.
	 * @return The new reference to the same object instance.
	 */
	@Inline
	@Override
	public ObjectReference traceObject(ObjectReference object) {
		//null
		if (object.isNull() || VM.objectModel.refToAddress(object).isZero()) {
			return object;
		}

		//our table
		if (object == Color.tableRef) {
			ScaleLogger.acquireLogLock();
			Log.prependThreadId();
			Log.write("SColorsTraceLocal.traceObject !!! color table ");
			Log.flush();
			VM.objectModel.dumpObject(object);
			Log.flush();
			ScaleLogger.releaseLogLock();
			//if(this.initialTrace) {
				return super.traceObject(object);
			//} else {
			//	return object;
			//}
		}
		
		//invalid
		if (!VM.objectModel.validRef(object)) {
			invalidTraced++;
			return object;
		}
		
		//other space
		if (!Space.isInSpace(SColors.SCALE_MS_SPACE, object)) {
			return super.traceObject(object);
		}
		
		if (Options.scaleDebug.getValue() > 5) {
			ScaleLogger.acquireLogLock();
			Log.prependThreadId();
			Log.write("SColorsTraceLocal.traceObject, object=");
			Log.flush();
			VM.objectModel.dumpObject(object);
			Log.flush();
			ScaleLogger.releaseLogLock();
		}
		
		//color reference
		if (Color.getColor(object) != null) {
			if (Options.scaleDebug.getValue() > 2) {
				// this can be caused by trying to trace through color ????
				ScaleLogger.acquireLogLock();
				Log.prependThreadId();
				Log.write("SColorsTraceLocal.traceObject !!! tracing through color, object=");
				Log.writeln(object);
				Log.flush();
				ScaleLogger.releaseLogLock();
			}
			return ObjectReference.nullReference();
		}
		
		if (currentClosureCounts == this.depthClosureCounts) {
			// this is the first trace, should not be any colors around
			if (VM.VERIFY_ASSERTIONS) {
				ObjectReference colorMark = VM.objectModel.getKathy(object);
				if (colorMark != ObjectReference.nullReference()) {
					ScaleLogger.acquireLogLock();
					Log.prependThreadId();
					Log.write("SColorsTraceLocal.traceObject !!! depth trace, object=");
					Log.write(object);
					Log.write(", colorMark=");
					Log.writeln(colorMark);
					Log.flush();
					VM.objectModel.dumpObject(object);
					ScaleLogger.releaseLogLock();
					VM.assertions._assert(
									false,
									"SColorsTraceLocal.traceObject - this is the first trace, should not be any colors around");
				}
			}
			return super.traceObject(object);
		}

		if (currentClosureCounts == this.closureCounts) {
			// this is the coloring trace, should look at colors before
			// marking
			return traceColor(object);
		}

		if (currentClosureCounts == this.checkClosureCounts) {
			if (Scale.isInCheckClosure()) {
				return traceCheck(object);
			}
			return super.traceObject(object);
		}
		
		//should not get here
		ScaleLogger.acquireLogLock();
		Log.prependThreadId();
		Log.write("SColorsTraceLocal.traceObject !!! should not get here");
		Log.writeln(object);
		Log.flush();
		ScaleLogger.releaseLogLock();
		return super.traceObject(object);
	}

	// single threaded, object is in our space
	private ObjectReference traceCheck(ObjectReference object) {
		//get the color mark
		if (Options.scaleDebug.getValue() > 5) {
			//ScaleLogger.acquireLogLock();	
			Log.write("TraceCheck ");
			Log.flush();
			VM.objectModel.dumpObject(object);
			//ScaleLogger.releaseLogLock();
		}
		ObjectReference colorMark = VM.objectModel.getKathy(object);
		int tcolor = Scale.scaleSpace.getColor(object);
		if (colorMark == ObjectReference.nullReference()) {
			if(tcolor == 0) {
				//dead
			} else if(tcolor == 1) {
				//live, not marked by tracers
			} else if(tcolor == 2) {
				//live, marked by tracers
			} else if(tcolor == 3) {
				//already marked by this trace
			}
			return SColors.scaleSpace.traceObject(this, object);
		}
		//get the color object
		Color color = Color.getColor(colorMark);
		if (color == null) {
			//sanity - replace with assertion
			Log.prependThreadId();
			Log.write("\t traceCheck !!! color is NULL for colorMark=");
			Log.write(colorMark);
			Log.write(", object=");
			Log.writeln(object);
			Log.flush();
			return SColors.scaleSpace.traceObject(this, object);
		}
		//color object properties
		boolean live = color.isLive();
		if (!live) {
			//should not happen, replace with assertion
			Log.prependThreadId();
			Log.writeln("traceCheck !!! color is dead, color=");
			color.dump();
			Log.flush();
			VM.objectModel.dumpObject(object);
			Log.flush();
			return SColors.scaleSpace.traceObject(this, object);
		}
		
		//colored live color, process
		/*
		byte[] type = VM.objectModel.getTypeDescriptor(object);
		if(compareType(type)) {
			Log.prependThreadId();
			Log.writeln("traceCheck *** LinkedHashMap ");
			color.dump();
			Log.flush();
			VM.objectModel.dumpObject(object);
			Log.flush();
			SColors.scaleSpace.traceObject(this, object);
		}*/
		
		int numSavedValues = color.getNumSavedValues();
		int numSavedOSObjects = color.getNumSavedOSObjects();
		long numColored = color.getColored();
		if (numColored > 0) {
			if (Options.scaleDebug.getValue() > 3) {
				Log.prependThreadId();
				Log.write("traceCheck colored object, color=");
				Log.write(colorMark.toAddress().toInt());
				Log.write(", numSavedValues=");
				Log.write(numSavedValues);
				Log.write(", numColored=");
				Log.write(numColored);
				Log.write(" ");
				Log.flush();
				VM.objectModel.dumpObject(object);
			}
			color.resetNumColored();
			int added = 0;
			if (numSavedValues > 0) {
				ObjectReferenceArray savedValues = color.getSavedValues();
				ObjectReference objectRef;
				for (int index = 0; index < numSavedValues; index++) {
					objectRef = savedValues.get(index);
					if (Options.scaleDebug.getValue() > 3) {
						Log.prependThreadId();
						Log.write("traceCheck color front object=");
						Log.flush();
						VM.objectModel.dumpObject(objectRef);
					}
					//SColors.scaleSpace.traceObject(this, objectRef);
					if(!SColors.scaleSpace.isLive(objectRef)) {
						added++;
						this.processNode(objectRef);
					}
					//this.scanObject(objectRef);
				}
				color.resetNumSavedValues();
			}
			if(numSavedOSObjects > 0) {
				ObjectReference objectRef;
				ObjectReferenceArray savedOSObjects = color.getOsObjects();
				for (int index = 0; index < numSavedOSObjects; index++) {
					objectRef = savedOSObjects.get(index);
					this.processNode(objectRef);
				}
			}
			currentClosureCounts.addWork(numColored - added);
		}
		
		return object;
	}

	/*
	private byte[] linkedHashMap = "LinkedHashMap".getBytes();
	private boolean compareType(byte[] type) {
		boolean result = true;
		int typeLength = type.length - 2;
		int controlLength = linkedHashMap.length - 1;
		for(int i = 0; i < controlLength; i++) {
			if(linkedHashMap[controlLength-i] != type[typeLength-i]) {
				result = false;
				break;
			}
		}
		return result;
	}
	*/
	
	private ObjectReference traceColor(ObjectReference object) {
		//if (SColors.scaleSpace.isLive(object)) {
			// traced by regular tracer - do nothing
			//return object;
		//}
		Helper.acquireHelpersLock();
		ObjectReference colorMark = VM.objectModel.getKathy(object);
		int regColorMark = SColors.scaleSpace.getColor(object);
		if(regColorMark == 0 && colorMark == ObjectReference.nullReference()) {
			//deadObject
			if (Options.scaleDebug.getValue() > 4) {
				ScaleLogger.acquireLogLock();
				Log.prependThreadId();
				Log.write("traceColor !!! tracing through dead ");
				Log.flush();
				VM.objectModel.dumpObject(object);
				Log.flush();
				ScaleLogger.releaseLogLock();
			}
			tracedDeadObjects++;
		}
		if (colorMark == ObjectReference.nullReference()) {
			// object is not traced by helpers, do regular trace
			ObjectReference result = SColors.scaleSpace.traceObject(this,object);
			Helper.releaseHelpersLock();
			return result;
		}
		Helper.releaseHelpersLock();
		// figure out who traced the object, do not follow up with regular trace
		Color color = Color.getColor(colorMark);
		if (color == null) {
			//sanity, replace with assertion
			ScaleLogger.acquireLogLock();
			Log.prependThreadId();
			Log.write("!!! HELPER is NULL for color=");
			Log.write(colorMark);
			Log.write(", object=");
			Log.writeln(object);
			Log.flush();
			ScaleLogger.releaseLogLock();
		} else {
			//mark color live
			if(color.markLive() && Options.scaleDebug.getValue() > 3) {
				ScaleLogger.acquireLogLock();
				Log.prependThreadId();
				Log.write("traceColor, marked live color=");
				Log.write(colorMark);
				Log.write(" ");
				Log.flush();
				VM.objectModel.dumpObject(object);
				Log.flush();
				ScaleLogger.releaseLogLock();
			}
			// SColors.scaleSpace.markObject(object);
		}
		return object;
	}

	protected void giveUpRoots() {
		this.rootLocations.flushLocal();
	}

	/**
	 * Finishing processing all GC work. This method iterates until all work
	 * queues are empty.
	 */
	@Inline
	public void completeTrace() {
		this.invalidTraced = 0;
		long valuesPopped = 0;
		long msValuesPopped = 0;

		if (!rootLocations.isEmpty()) {
			if (Options.scaleDebug.getValue() > 5) {
				ScaleLogger.acquireLogLock();
				Log.prependThreadId();
				Log.writeln("SColorsTraceLocal.completeTrace - need to process roots");
				ScaleLogger.releaseLogLock();
			}
			processRoots();
		}

		assertMutatorRemsetsFlushed();
		do {
			while (!values.isEmpty()) {
				ObjectReference v = values.pop();
				if (Options.scaleDebug.getValue() > 5) {
					ScaleLogger.acquireLogLock();
					Log.prependThreadId();
					Log.write("SColorsTraceLocal.completeTrace - popped v=");
					Log.flush();
					VM.objectModel.dumpObject(v);
					Log.flush();
					ScaleLogger.releaseLogLock();
				}
				valuesPopped++;
				if(Space.isInSpace(SColors.SCALE_MS_SPACE, v)) {
					msValuesPopped++;
				}
				scanObject(v);
			}
			processRememberedSets();
		} while (!values.isEmpty());
		
		if (Options.scaleDebug.getValue() > 3) {
			ScaleLogger.acquireLogLock();
			Log.prependThreadId();
			Log.write("SColorsTraceLocal.completeTrace exit, valuesPopped=");
			Log.write(valuesPopped);
			Log.write(", msValuesPopped=");
			Log.write(msValuesPopped);
			Log.write(", invalidTraced=");
			Log.writeln(invalidTraced);
			ScaleLogger.releaseLogLock();
		}
		assertMutatorRemsetsFlushed();
	}

	@Override
	protected void doSetClosureCounts() {
		if (SColors.getTraceFlavor() == SColors.METRIX_COLOR_FLAVOR) {
			if (SColors.closure == SColors.CLOSURE_FIRST_SINGLE) {
				this.initialTrace = true;
				currentClosureCounts = this.depthClosureCounts;
			} else if (SColors.closure == SColors.CLOSURE_CONCURRENT) {
				this.initialTrace = false;
				currentClosureCounts = this.closureCounts;
			} else if (SColors.closure == SColors.CLOSURE_LAST_SINGLE) {
				this.initialTrace = false;
				currentClosureCounts = this.checkClosureCounts;
			} else if (VM.VERIFY_ASSERTIONS) {
				VM.assertions._assert(false,
								"SColorsTraceLocal.doSetClosureCounts encountered wrong closure");
			}
		}
	}

	public void flushHelpers() {
		if (Options.scaleDebug.getValue() > 5) {
			ScaleLogger
					.logConcMessage("SClolorsCollector.flushHelpers - enter");
		}
		if (currentClosureCounts == closureCounts) {
			if (Options.scaleDebug.getValue() > 5) {
				logMessage("\tSColorsTraceLocal.flushHelpers - flushing values");
				values.flushLocal();
			}
			if (Options.scaleDebug.getValue() > 5) {
				logMessage("\tSColorsTraceLocal.flushHelpers - flushing rootLocations");
				rootLocations.flushLocal();
			}
		}
		if (Options.scaleDebug.getValue() > 5) {
			ScaleLogger.logConcMessage("SClolorsCollector.flushHelpers - exit");
		}
	}

	public void completeColorsTrace() {
		if (Options.scaleDebug.getValue() > 3) {
			ScaleLogger.logConcMessage("SClolorsCollector.completeColorsTrace - enter");
		}

		Color color = Color.obtainColorToComplete();
		ObjectReferenceArray savedValues;
		int numSavedValues;
		int index;
		ObjectReference objRef;
		while (color != null) {
			if (Options.scaleDebug.getValue() > 5) {
				ScaleLogger.acquireLogLock();

				Log.prependThreadId();
				Log.write("\t colorMark=");
				Log.writeln(color.getColorMark());
				Log.flush();

				ScaleLogger.releaseLogLock();
			}
			savedValues = color.getSavedValues();
			numSavedValues = color.getNumSavedValues();
			for (index = 0; index < numSavedValues; index++) {
				objRef = savedValues.get(index);
				this.processNode(objRef);
			}
			this.completeTrace();
			color = Color.obtainColorToComplete();
		}

		this.completeTrace();
		
		if (Options.scaleDebug.getValue() > 5) {
			ScaleLogger.logConcMessage("SClolorsCollector.completeColorsTrace - exit");
		}
	}
	
	@Override
	public void prepare() {
		super.prepare();
		this.tracedDeadObjects = 0;
	}
}
